package math.vectorSpace.R2;

import math.mathExtended.MathExtended;
import math.vectorSpace.R3.Point3D;
import myAnnotations.Constant;
import myAnnotations.MutateSelf;

/**
 * Class for creating and working with points in R2.
 * Contains extensive methods for manipulating points.
 * 
 * @author Shimu Wu
 * @version 1.0
 */
public class Point2D {

    private double x, y;

    /***
     * Creates a point at origin
     */
    public Point2D() {
        this.x = 0;
        this.y = 0;
    }

    /**
     * Creates a point at the specified x, y coordinate
     * 
     * @param x the x-coordinate
     * @param y the y-coordinate
     */
    public Point2D(double x, double y) {
        this.x = x;
        this.y = y;
    }

    /**
     * Creates a point at the location of the given point.
     * 
     * @param newPoint a Point2D object
     */
    public Point2D(Point2D newPoint) {
        this.x = newPoint.x;
        this.y = newPoint.y;
    }

    /**
     * Creates a point at the head of the given vector with the tail at the origin.
     * 
     * @param vector a {@link Vector2D} object
     */
    public Point2D(Vector2D vector) {
        this.x = vector.getX();
        this.y = vector.getY();
    }

    /**
     * Returns the y-coordinate of this point.
     * 
     * @return the y-coordinate of this point
     */
    public double getX() {
        return x;
    }

    /**
     * Returns the y-coordinate of this point.
     * 
     * @return the y-coordinate of this point
     */
    public double getY() {
        return y;
    }

    /**
     * Returns true if this point is an invalid point, false otherwise.
     * An invalid point is returned from methods that cannot retrieve a
     * valid point. For example, if a method tries to find the point
     * of intersection between two lines, and is unable to, the method
     * would return an invalid point, which is simply created by
     * Point2D (Double.NaN, Double.NaN)
     * 
     * @return true if this point is a null point, false otherwise
     */
    public boolean isInvalidPoint() {
        if (Double.isNaN(x)) {
            return true;
        }
        return false;
    }

    @MutateSelf
    public void add(double x, double y) {
        this.x += x;
        this.y += y;
    }
    
    /** 
     * Adds the coordinates of the given point to this point. This method is 
     * useful for moving a point. If moving only in one axis is required, the 
     * other coordinates can simply be set to zero. If subtracting is required 
     * simply set the coordinates to negative, the subtract method can also be 
     * used.
     * 
     * @param point a Point2D
     * @return 
     * @see #add(Vector2D)
     * @see #subtract(Point2D)
     */
    @MutateSelf
    public void add(Point2D point) {
        add(point.x, point.y);
    }

    /** 
     * Adds the coordinates of the given point to this point.
     *  
     * @param vector a Point2D
     * @return 
     * @see #add(Point2D)
     */
    @MutateSelf
    public void add(Vector2D vector) {
        add(vector.getX(), vector.getY());
    }
    
    /**
     * Given two points returns a new Point2D that is the sum of the two points.
     * 
     * @param p1
     * @param p2
     * @return 
     */
    @Constant
    public static Point2D add(Point2D p1, Point2D p2) {
        Point2D newPoint = p1.copy();
        newPoint.add(p2);
        return newPoint;
    }
    
    /**
     * Given a point and a vector, returns a new Point2D that represents the
     * resulting point if one were to start at the given point and move
     * according to the given vector. An example would be to give a
     * starting location and a displacement vector, and receive the final 
     * location as the result.
     * 
     * @param p1
     * @param p2
     * @return 
     */
    @Constant
    public static Point2D add(Point2D p, Vector2D v) {
        Point2D newPoint = p.copy();
        newPoint.add(v);
        return newPoint;
    }

    /**
     * Subtracts the coordinates of the given point from this point.
     * 
     * @param point a Point3D
     * @see #add(Point3D)
     *
     */
    @MutateSelf
    public void subtract(Point2D point) {
        this.x -= point.x;
        this.y -= point.y;
    }

    @MutateSelf
    public void multiply(Point2D point) {
        this.x *= point.x;
        this.y *= point.y;
    }

    /**
     * Returns the distance from this point to the given (x, y) coordinate
     * 
     * @param x
     * @param y
     * @return 
     */
    @Constant
    public double distance(double x, double y) {
        return MathExtended.distance(x - this.x, y - this.y);
    }
    
    /**
     * Returns the distance from this point to the given point.
     * 
     * @param point2 a Point2D
     * @return the distance between this point and the given point.
     */
    @Constant
    public double distance(Point2D point2) {
        return distance(point2.getX(), point2.getY());
    }

    /**
     * Returns the distance from this point to the origin.
     * 
     * @return the distance between this point and the origin.
     */
    @Constant
    public double distance() {
        return distance(0, 0);
    }

    /**
     * Returns a copy of this Point2D
     * 
     * @return 
     */
    @Constant
    public Point2D copy() {
        return new Point2D(this.x, this.y);
    }

    /**
     * Returns the String representation of this point of the form: Point2D (x, y).
     * 
     * @return the String representation of this point
     */
    @Override
    public String toString() {
        return ("Point2D (" + Double.toString(this.x) + ", "
                + Double.toString(this.y) + ")");
    }
}
